package com.atguigu.tingshu.order.adapter;
import java.util.Date;
import java.math.BigDecimal;
import java.util.List;


import com.atguigu.tingshu.account.client.UserAccountFeignClient;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.rabbit.constant.MqConst;
import com.atguigu.tingshu.common.rabbit.service.RabbitService;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.model.order.LocalMsg;
import com.atguigu.tingshu.order.mapper.LocalMsgMapper;
import com.atguigu.tingshu.order.service.OrderInfoService;
import com.atguigu.tingshu.vo.account.AccountLockResultVo;
import com.atguigu.tingshu.vo.account.AccountLockVo;
import com.atguigu.tingshu.vo.order.OrderInfoVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class RechargePayWayImpl implements PayWay {

    @Autowired
    private OrderInfoService orderInfoService;

    @Autowired
    private UserAccountFeignClient userAccountFeignClient;

    @Autowired
    private RabbitService rabbitService;

    @Autowired
    private LocalMsgMapper localMsgMapper;


    /**
     * 启动一个定时任务(做兜底),这个定时任务一直给消息队列发送消息
     */
    @Scheduled(fixedDelay = 1000*60*60)
    public void retryMsg(){

        List<LocalMsg> localMsgs = localMsgMapper.selectList(new LambdaQueryWrapper<LocalMsg>().eq(LocalMsg::getStatus, 0));
        if (localMsgs==null||localMsgs.size()==0){
            return;
        }
        for (LocalMsg localMsg : localMsgs) {
            String orderNo = localMsg.getMsgContent();
            //未来每次定时任务启动的时候都会发送3次
            rabbitService.sendMessage(MqConst.EXCHANGE_ACCOUNT,MqConst.ROUTING_ACCOUNT_MINUS,orderNo);
        }


    }

    /**
     * 使用分布式事务的@GlobalTransactional注解:(该注解不是springboot的也不是spring的)(它是seata中间件带的)
     * 1)该注解可以保证在rpc远程调用过程中对不同库的不同表的使用期间,有任意一个库的操作出现失败,另一个操作成功的库也会回滚
     * 2)自己通过异常机制来实现回滚
     *
     * 使用rpc远程调用引起的分布式事务问题的解锁方案:
     * 1)引入seata中间件(异常机制+反向补偿:undolog中取之前没有删除的数据)
     * 2)异常机制+反向补偿(redis中获取的数据)
     *
     * <p>
     * 一：使用rpc远程调用引起的分布式事务问题解决方案：
     * XA:
     * AT:(首推) 保证强一致
     * TCC:（最终一致）
     * <p>
     * 1.引入seata（异常机制+反向补偿：undolog中取之前没删除的数据）
     * 2.异常机制+反向补偿（redis中获取的数据）
     * <p>
     * 二：
     * 使用消息队列引起的分布式事务问题解决方案：
     * 上游重试机制+定时任务做（兜底）+本地消息表做控制
     * <p>
     * @param userId
     * @param orderNo
     * @param orderInfoVo
     */
    @Override
    public void payWay(Long userId, String orderNo, OrderInfoVo orderInfoVo) {
        //零钱的支付逻辑


        //1.准备远程调用账号微服务锁定账户的数据
         AccountLockVo accountLockVo =preParecheckAndLockUserAccount(userId,orderNo,orderInfoVo);

        //2.校验账户的余额是否充足,并且锁定账户的余额
        //立刻就要用,这里必须用rpc远程调用账号微服务
        Result<AccountLockResultVo> checkAndLockResult =userAccountFeignClient.checkAndLockUserAccount(accountLockVo);
        Integer code = checkAndLockResult.getCode();
        if (code!=200){
            throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_ERROR);
        }

        try {
            //3.保存订单信息
            orderInfoService.saveOrderInfo(orderNo,userId,orderInfoVo);

            //4.消费(真正扣钱,解锁) -->只要保证最终余额减掉就可以了,所以不用远程调用,可以使用异步消息队列
            // 注意:消息队列为了保证最终一致性,不可能只发送一次扣钱,而是发送多次扣钱,保证最终扣钱

            rabbitService.sendMessage(MqConst.EXCHANGE_ACCOUNT,MqConst.ROUTING_ACCOUNT_MINUS,orderNo);

        } catch (GuiguException e) {
            //回滚
            //异步通过消息队列让下游自己解锁2张表
            rabbitService.sendMessage(MqConst.EXCHANGE_ACCOUNT,MqConst.ROUTING_ACCOUNT_UNLOCK,orderNo);
        }catch (Exception e){
            //rabbitMQ的sendMessage的底层里会发送3次消息不能保证一定能成功
            //反向的消费,但是它只能发三次,这个起一个定时任务一直发
            rabbitService.sendMessage(MqConst.EXCHANGE_ACCOUNT,MqConst.ROUTING_ACCOUNT_MINUS,orderNo);
        }

    }

    /**
     * 准备远程调用账号微服务锁定账户的需要的参数数据
     * @param userId
     * @param orderNo
     * @param orderInfoVo
     * @return
     */
    private AccountLockVo preParecheckAndLockUserAccount(Long userId, String orderNo, OrderInfoVo orderInfoVo) {
        AccountLockVo accountLockVo = new AccountLockVo();
        accountLockVo.setOrderNo(orderNo);
        accountLockVo.setUserId(userId);
        accountLockVo.setAmount(orderInfoVo.getOrderAmount());
        accountLockVo.setContent(orderInfoVo.getOrderDetailVoList().get(0).getItemName());

        return accountLockVo;
    }

    @Override
    public boolean isSupport(String payWay) {
        //具体适配的逻辑
        return "1103".equals(payWay);
    }
}
